{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rK1pP01MMuU1"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "gtl722MvjuSf"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "F9AnjBfz22gq"
      },
      "source": [
        "# Keras 모델 저장 및 로드"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TrNGttwSFElt"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/save_and_serialize\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/guide/keras/save_and_serialize.ipynb\" class=\"\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/guide/keras/save_and_serialize.ipynb\" class=\"\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서 소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/guide/keras/save_and_serialize.ipynb\" class=\"\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PlYTaLGGOlmx"
      },
      "source": [
        "## 소개\n",
        "\n",
        "Keras 모델은 다중 구성 요소로 이루어집니다.\n",
        "\n",
        "- 모델에 포함된 레이어 및 레이어의 연결 방법을 지정하는 아키텍처 또는 구성\n",
        "- 가중치 값의 집합(\"모델의 상태\")\n",
        "- 옵티마이저(모델을 컴파일하여 정의)\n",
        "- 모델을 컴파일링하거나 `add_loss()` 또는 `add_metric()`을 호출하여 정의된 손실 및 메트릭의 집합\n",
        "\n",
        "Keras API를 사용하면 이러한 조각을 한 번에 디스크에 저장하거나 선택적으로 일부만 저장할 수 있습니다.\n",
        "\n",
        "- TensorFlow SavedModel 형식(또는 이전 Keras H5 형식)으로 모든 것을 단일 아카이브에 저장합니다. 이것이 표준 관행입니다.\n",
        "- 일반적으로 JSON 파일로 아키텍처 및 구성만 저장합니다.\n",
        "- 가중치 값만 저장합니다. 이것은 일반적으로 모델을 훈련할 때 사용됩니다.\n",
        "\n",
        "언제 사용해야 하는지, 어떻게 동작하는 것인지 각각 살펴봅시다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EKhPbck9E82N"
      },
      "source": [
        "## 저장 및 로딩에 대한 짧은 답변\n",
        "\n",
        "다음은 이 가이드를 읽는데 10초 밖에 없는 경우 알아야 할 사항입니다.\n",
        "\n",
        "**Keras 모델 저장하기**\n",
        "\n",
        "```python\n",
        "model = ...  # Get model (Sequential, Functional Model, or Model subclass) model.save('path/to/location')\n",
        "```\n",
        "\n",
        "**모델을 다시 로딩하기**\n",
        "\n",
        "```python\n",
        "from tensorflow import keras model = keras.models.load_model('path/to/location')\n",
        "```\n",
        "\n",
        "이제 세부 사항을 확인해봅시다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bT80eTSUngCU"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "BallmpGiEbXD"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "from tensorflow import keras"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rZ6eEK8ekthu"
      },
      "source": [
        "## 전체 모델 저장 및 로딩\n",
        "\n",
        "전체 모델을 단일 아티팩트로 저장할 수 있습니다. 다음을 포함합니다.\n",
        "\n",
        "- 모델의 아키텍처 및 구성\n",
        "- 훈련 중에 학습된 모델의 가중치 값\n",
        "- 모델의 컴파일 정보(`compile()`이 호출된 경우)\n",
        "- 존재하는 옵티마이저와 그 상태(훈련을 중단한 곳에서 다시 시작할 수 있게 해줌)\n",
        "\n",
        "#### APIs\n",
        "\n",
        "- `model.save()` 또는 `tf.keras.models.save_model()`\n",
        "- `tf.keras.models.load_model()`\n",
        "\n",
        "전체 모델을 디스크에 저장하는 데 사용할 수 있는 두 형식은 **TensorFlow SavedModel 형식**과 **이전 Keras H5 형식**입니다. 권장하는 형식은 SavedModel입니다. 이는 `model.save()`를 사용할 때의 기본값입니다.\n",
        "\n",
        "다음을 통해 H5 형식으로 전환할 수 있습니다.\n",
        "\n",
        "- `format='h5'`를 `save()`로 전달합니다.\n",
        "- `.h5` 또는 `.keras`로 끝나는 파일명을 `save()`로 전달합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HUg5WkAAObZn"
      },
      "source": [
        "### SavedModel 형식\n",
        "\n",
        "**예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MsqSBTGkkGma"
      },
      "outputs": [],
      "source": [
        "def get_model():\n",
        "    # Create a simple model.\n",
        "    inputs = keras.Input(shape=(32,))\n",
        "    outputs = keras.layers.Dense(1)(inputs)\n",
        "    model = keras.Model(inputs, outputs)\n",
        "    model.compile(optimizer=\"adam\", loss=\"mean_squared_error\")\n",
        "    return model\n",
        "\n",
        "\n",
        "model = get_model()\n",
        "\n",
        "# Train the model.\n",
        "test_input = np.random.random((128, 32))\n",
        "test_target = np.random.random((128, 1))\n",
        "model.fit(test_input, test_target)\n",
        "\n",
        "# Calling `save('my_model')` creates a SavedModel folder `my_model`.\n",
        "model.save(\"my_model\")\n",
        "\n",
        "# It can be used to reconstruct the model identically.\n",
        "reconstructed_model = keras.models.load_model(\"my_model\")\n",
        "\n",
        "# Let's check:\n",
        "np.testing.assert_allclose(\n",
        "    model.predict(test_input), reconstructed_model.predict(test_input)\n",
        ")\n",
        "\n",
        "# The reconstructed model is already compiled and has retained the optimizer\n",
        "# state, so training can resume:\n",
        "reconstructed_model.fit(test_input, test_target)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "onibKMsFZ4Bk"
      },
      "source": [
        "#### SavedModel이 포함하는 것\n",
        "\n",
        "`model.save('my_model')`을 호출하면 다음을 포함하는  `my_model` 폴더를 생성합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "o0kniAGdvEmH"
      },
      "outputs": [],
      "source": [
        "!ls my_model"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9gZk3nwEHKCt"
      },
      "source": [
        "모델 아키텍처 및 훈련 구성(옵티마이저, 손실 및 메트릭 포함)은 `saved_model.pb`에 저장됩니다. 가중치는 `variables/` 디렉토리에 저장됩니다.\n",
        "\n",
        "SavedModel 형식에 대한 자세한 내용은 [SavedModel 가이드(*디스크의 SavedModel 형식*](https://www.tensorflow.org/guide/saved_model#the_savedmodel_format_on_disk))를 참조하세요.\n",
        "\n",
        "#### SavedModel이 사용자 정의 객체를 처리하는 방법\n",
        "\n",
        "모델과 모델의 레이어를 저장할 때 SavedModel 형식은 클래스명, **호출 함수**, 손실 및 가중치(구현된 경우에는 구성도 포함)를 저장합니다. 호출 함수는 모델/레이어의 계산 그래프를 정의합니다.\n",
        "\n",
        "모델/레이어 구성이 없는 경우 호출 함수는 훈련, 평가 및 추론에 사용될 수 있는 기존 모델과 같은 모델을 만드는 데 사용됩니다.\n",
        "\n",
        "그럼에도 불구하고 사용자 정의 모델 또는 레이어 클래스를 작성할 때 항상 `get_config` 및 `from_config` 메서드를 정의하는 것이 좋습니다. 이를 통해 필요한 경우 나중에 계산을 쉽게 업데이트할 수 있습니다. 자세한 내용은 [사용자 정의 객체](save_and_serialize.ipynb#custom-objects)에 대한 섹션을 참조하세요.\n",
        "\n",
        "다음은 구성 메서드를 덮어쓰지**않고** SavedModel 형식에서 사용자 정의 레이어를 로딩할 때 발생하는 현상에 대한 예제입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "PPIAXT8BFSf9"
      },
      "outputs": [],
      "source": [
        "class CustomModel(keras.Model):\n",
        "    def __init__(self, hidden_units):\n",
        "        super(CustomModel, self).__init__()\n",
        "        self.dense_layers = [keras.layers.Dense(u) for u in hidden_units]\n",
        "\n",
        "    def call(self, inputs):\n",
        "        x = inputs\n",
        "        for layer in self.dense_layers:\n",
        "            x = layer(x)\n",
        "        return x\n",
        "\n",
        "\n",
        "model = CustomModel([16, 16, 10])\n",
        "# Build the model by calling it\n",
        "input_arr = tf.random.uniform((1, 5))\n",
        "outputs = model(input_arr)\n",
        "model.save(\"my_model\")\n",
        "\n",
        "# Delete the custom-defined model class to ensure that the loader does not have\n",
        "# access to it.\n",
        "del CustomModel\n",
        "\n",
        "loaded = keras.models.load_model(\"my_model\")\n",
        "np.testing.assert_allclose(loaded(input_arr), outputs)\n",
        "\n",
        "print(\"Original model:\", model)\n",
        "print(\"Loaded model:\", loaded)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WnESi1jRVLHz"
      },
      "source": [
        "위 예제에서 볼 수 있듯이 로더는 기존 모델처럼 작동하는 새 모델 클래스를 동적으로 만듭니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "STywDB8VW8gu"
      },
      "source": [
        "### Keras H5 형식\n",
        "\n",
        "Keras는 또한 모델의 아키텍처, 가중치 값 및 `compile()` 정보가 포함된 단일 HDF5 파일 저장을 지원합니다. SavedModel에 대한 가벼운 대안입니다.\n",
        "\n",
        "**예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gRIvOIfqWhQJ"
      },
      "outputs": [],
      "source": [
        "model = get_model()\n",
        "\n",
        "# Train the model.\n",
        "test_input = np.random.random((128, 32))\n",
        "test_target = np.random.random((128, 1))\n",
        "model.fit(test_input, test_target)\n",
        "\n",
        "# Calling `save('my_model.h5')` creates a h5 file `my_model.h5`.\n",
        "model.save(\"my_h5_model.h5\")\n",
        "\n",
        "# It can be used to reconstruct the model identically.\n",
        "reconstructed_model = keras.models.load_model(\"my_h5_model.h5\")\n",
        "\n",
        "# Let's check:\n",
        "np.testing.assert_allclose(\n",
        "    model.predict(test_input), reconstructed_model.predict(test_input)\n",
        ")\n",
        "\n",
        "# The reconstructed model is already compiled and has retained the optimizer\n",
        "# state, so training can resume:\n",
        "reconstructed_model.fit(test_input, test_target)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bjxsX8XdS4Oj"
      },
      "source": [
        "#### 제한 사항\n",
        "\n",
        "SavedModel 형식과 비교하여 H5 파일에 포함되지 않은 두 가지가 있습니다.\n",
        "\n",
        "- `model.add_loss()` 및 `model.add_metric()`을 통해 추가된 **외부 손실 및 메트릭**은 SavedModel과 달리 저장되지 않습니다. 모델에 이러한 손실 및 메트릭이 있고 훈련을 재개하려는 경우 모델을 로드한 후 이러한 손실을 다시 추가해야 합니다. `self.add_loss()` 및 `self.add_metric()`을 통해 레이어 *내부에* 생성된 손실/메트릭에는 적용되지 않습니다. 레이어가 로드되는 한 이러한 손실 및 메트릭은 레이어의 `call` 메서드의 일부이므로 유지됩니다.\n",
        "- 사용자 정의 레이어와 같은 **사용자 정의 객체의 계산 그래프**는 저장된 파일을 포함하지 않습니다. 로딩 시 Keras는 모델을 재구성하기 위해 이러한 객체의 Python 클래스/함수에 접근해야 합니다. [사용자 정의 객체](save_and_serialize.ipynb#custom-objects)를 참조하세요.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cY3tXyZyk4Ws"
      },
      "source": [
        "## 아키텍처 저장\n",
        "\n",
        "모델의 구성(또는 아키텍처)은 모델에 포함된 레이어와 이러한 레이어의 연결 방법*을 지정합니다. 모델 구성이 있는 경우 가중치에 대해 새로 초기화된 상태로 컴파일 정보 없이 모델을 작성할 수 있습니다.\n",
        "\n",
        "*이 기능은 서브 클래스 모델이 아닌 함수형 또는 Sequential API를 사용하여 정의된 모델에만 적용됩니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rIjTX1Z0ljoo"
      },
      "source": [
        "### Sequential 모델 또는 Functional API 모델의 구성\n",
        "\n",
        "이러한 유형의 모델은 레이어의 명시적 그래프입니다. 구성은 항상 구조화된 형식으로 제공됩니다.\n",
        "\n",
        "#### APIs\n",
        "\n",
        "- `get_config()` 및 `from_config()`\n",
        "- `tf.keras.models.model_to_json()` 및 `tf.keras.models.model_from_json()`"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "F7V8jN9nt9hB"
      },
      "source": [
        "#### `get_config()` 및 `from_config()`\n",
        "\n",
        "`config = model.get_config()`을 호출하면 모델 구성이 포함된 Python dict가 반환됩니다. 그런 다음 `Sequential.from_config(config)`(`Sequential` 모델) 또는 `Model.from_config(config)`(Functional API 모델)를 통해 동일한 모델을 재구성할 수 있습니다.\n",
        "\n",
        "모든 직렬화 가능 레이어에 대해서도 같은 워크플로가 작동합니다.\n",
        "\n",
        "**레이어 예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "E4H3XIDY91oy"
      },
      "outputs": [],
      "source": [
        "layer = keras.layers.Dense(3, activation=\"relu\")\n",
        "layer_config = layer.get_config()\n",
        "new_layer = keras.layers.Dense.from_config(layer_config)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2orPhGTaHZRX"
      },
      "source": [
        "**Sequential 모델 예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "F09I6yvGV2uf"
      },
      "outputs": [],
      "source": [
        "model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])\n",
        "config = model.get_config()\n",
        "new_model = keras.Sequential.from_config(config)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Q9SuxM15lEUr"
      },
      "source": [
        "**Functional 모델 예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HHIVpEKSsT8o"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input((32,))\n",
        "outputs = keras.layers.Dense(1)(inputs)\n",
        "model = keras.Model(inputs, outputs)\n",
        "config = model.get_config()\n",
        "new_model = keras.Model.from_config(config)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NDjRR6fO4GS6"
      },
      "source": [
        "#### `to_json()` 및 `tf.keras.models.model_from_json()`\n",
        "\n",
        "이것은 `get_config` / `from_config`와 비슷하지만, 모델을 JSON 문자열로 변환한 다음 기존 모델 클래스 없이 로드할 수 있습니다. 또한, 모델에만 해당하며 레이어용이 아닙니다.\n",
        "\n",
        "**예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "J7jcVOpdPRie"
      },
      "outputs": [],
      "source": [
        "model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])\n",
        "json_config = model.to_json()\n",
        "new_model = keras.models.model_from_json(json_config)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WE6kPB1B8Xy5"
      },
      "source": [
        "### 사용자 정의 객체\n",
        "\n",
        "**모델과 레이어**\n",
        "\n",
        "서브 클래스 모델과 레이어의 아키텍처는 `__init__` 및 `call` 메서드에 정의되어 있습니다. 그것들은 Python 바이트 코드로 간주하며 JSON 호환 구성으로 직렬화할 수 없습니다 -- 바이트 코드 직렬화를 시도할 수는 있지만(예: `pickle`을 통해) 완전히 불안전하므로 모델을 다른 시스템에 로드할 수 없습니다.\n",
        "\n",
        "사용자 정의 레이어를 사용하는 모델 또는 서브 클래스 모델을 저장/로드하려면 `get_config` 및 선택적으로 `from_config` 메서드를 덮어써야 합니다. 또한 Keras가 인식할 수 있도록 사용자 정의 객체를 등록해야 합니다.\n",
        "\n",
        "**사용자 정의 함수**\n",
        "\n",
        "사용자 정의 함수(예: 활성화 손실 또는 초기화)에는 `get_config` 메서드가 필요하지 않습니다. 함수명은 사용자 정의 객체로 등록되어 있는 한 로드하기에 충분합니다.\n",
        "\n",
        "**TensorFlow 그래프만 로딩하기**\n",
        "\n",
        "Keras가 생성한 TensorFlow 그래프를 로드할 수 있습니다. 그렇게 하면 `custom_objects`를 제공할 필요가 없습니다. 다음과 같이 해볼 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "znOcN8keiaaD"
      },
      "outputs": [],
      "source": [
        "model.save(\"my_model\")\n",
        "tensorflow_graph = tf.saved_model.load(\"my_model\")\n",
        "x = np.random.uniform(size=(4, 32)).astype(np.float32)\n",
        "predicted = tensorflow_graph(x).numpy()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ovu5chswcHzn"
      },
      "source": [
        "이 메서드에는 몇 가지 단점이 있습니다.\n",
        "\n",
        "- 추적 가능성을 위해 사용된 사용자 정의 객체에 항상 접근할 수 있어야 합니다. 다시 만들 수 없는 모델을 제품에 넣고 싶지 않을 것입니다.\n",
        "- `tf.saved_model.load`에 의해 반환된 객체는 Keras 모델이 아닙니다. 따라서 사용하기가 쉽지 않습니다. 예를 들면, `.predict()` 또는 `.fit()`에 접근할 수 없습니다.\n",
        "\n",
        "사용을 권장하지는 않지만, 사용자 정의 객체의 코드를 잃어버렸거나 `tf.keras.models.load_model()` 모델을 로드하는 데 문제가 있는 경우와 같이 곤란한 상황에서는 도움이 될 수 있습니다.\n",
        "\n",
        "`tf.saved_model.load`와 관련된 [페이지](https://www.tensorflow.org/api_docs/python/tf/saved_model/load)에서 자세한 내용을 확인할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "B5p8XgNCi0Sm"
      },
      "source": [
        "#### 구성 메서드 정의하기\n",
        "\n",
        "명세:\n",
        "\n",
        "- `get_config`는 Keras 아키텍처 및 모델 저장 API와 호환되도록 JSON 직렬화 가능 사전을 반환해야 합니다.\n",
        "- `from_config(config)`(`classmethod`)는 구성에서 생성된 새 레이어 또는 모델 객체를 반환해야 합니다. 기본 구현은 `cls(**config)`를 반환합니다.\n",
        "\n",
        "**예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YeVMs9Rs5ojC"
      },
      "outputs": [],
      "source": [
        "class CustomLayer(keras.layers.Layer):\n",
        "    def __init__(self, a):\n",
        "        self.var = tf.Variable(a, name=\"var_a\")\n",
        "\n",
        "    def call(self, inputs, training=False):\n",
        "        if training:\n",
        "            return inputs * self.var\n",
        "        else:\n",
        "            return inputs\n",
        "\n",
        "    def get_config(self):\n",
        "        return {\"a\": self.var.numpy()}\n",
        "\n",
        "    # There's actually no need to define `from_config` here, since returning\n",
        "    # `cls(**config)` is the default behavior.\n",
        "    @classmethod\n",
        "    def from_config(cls, config):\n",
        "        return cls(**config)\n",
        "\n",
        "\n",
        "layer = CustomLayer(5)\n",
        "layer.var.assign(2)\n",
        "\n",
        "serialized_layer = keras.layers.serialize(layer)\n",
        "new_layer = keras.layers.deserialize(\n",
        "    serialized_layer, custom_objects={\"CustomLayer\": CustomLayer}\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OlbIz9cmWDsr"
      },
      "source": [
        "#### 사용자 정의 객체 등록하기\n",
        "\n",
        "Keras는 구성을 생성한 클래스를 기록합니다. 위의 예에서 `tf.keras.layers.serialize`는 사용자 정의 레이어의 직렬화된 형식을 생성합니다.\n",
        "\n",
        "```\n",
        "{'class_name': 'CustomLayer', 'config': {'a': 2}}\n",
        "```\n",
        "\n",
        "`from_config`를 호출할 올바른 클래스를 찾는 데 사용되는 모든 내장 레이어, 모델, 옵티마이저 및 메트릭 클래스의 마스터 목록을 유지합니다. 클래스를 찾을 수 없으면 오류가 발생합니다(`Value Error: Unknown layer`). 다음 목록은 사용자 정의 클래스를 등록하는 몇 가지 방법입니다.\n",
        "\n",
        "1. 로딩 함수에서 `custom_objects` 인수 설정(위의 \"구성 메서드 정의하기\" 섹션의 예 참조).\n",
        "2. `tf.keras.utils.custom_object_scope` 또는 `tf.keras.utils.CustomObjectScope`\n",
        "3. `tf.keras.utils.register_keras_serializable`"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5X5chZaxYpC2"
      },
      "source": [
        "#### 사용자 정의 레이어 및 함수 예제"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MdYdOM5u4NJ9"
      },
      "outputs": [],
      "source": [
        "class CustomLayer(keras.layers.Layer):\n",
        "    def __init__(self, units=32, **kwargs):\n",
        "        super(CustomLayer, self).__init__(**kwargs)\n",
        "        self.units = units\n",
        "\n",
        "    def build(self, input_shape):\n",
        "        self.w = self.add_weight(\n",
        "            shape=(input_shape[-1], self.units),\n",
        "            initializer=\"random_normal\",\n",
        "            trainable=True,\n",
        "        )\n",
        "        self.b = self.add_weight(\n",
        "            shape=(self.units,), initializer=\"random_normal\", trainable=True\n",
        "        )\n",
        "\n",
        "    def call(self, inputs):\n",
        "        return tf.matmul(inputs, self.w) + self.b\n",
        "\n",
        "    def get_config(self):\n",
        "        config = super(CustomLayer, self).get_config()\n",
        "        config.update({\"units\": self.units})\n",
        "        return config\n",
        "\n",
        "\n",
        "def custom_activation(x):\n",
        "    return tf.nn.tanh(x) ** 2\n",
        "\n",
        "\n",
        "# Make a model with the CustomLayer and custom_activation\n",
        "inputs = keras.Input((32,))\n",
        "x = CustomLayer(32)(inputs)\n",
        "outputs = keras.layers.Activation(custom_activation)(x)\n",
        "model = keras.Model(inputs, outputs)\n",
        "\n",
        "# Retrieve the config\n",
        "config = model.get_config()\n",
        "\n",
        "# At loading time, register the custom objects with a `custom_object_scope`:\n",
        "custom_objects = {\"CustomLayer\": CustomLayer, \"custom_activation\": custom_activation}\n",
        "with keras.utils.custom_object_scope(custom_objects):\n",
        "    new_model = keras.Model.from_config(config)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ia1JUuCjy70o"
      },
      "source": [
        "### 인메모리 모델 복제\n",
        "\n",
        "`tf.keras.models.clone_model()`을 통해 모델의 인메모리 복제를 수행할 수도 있습니다. 이는 구성을 가져온 다음 구성에서 모델을 다시 생성하는 것과 같습니다(따라서 컴파일 정보 또는 레이어 가중치 값을 유지하지 않습니다).\n",
        "\n",
        "**예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "16KQFlItCZf2"
      },
      "outputs": [],
      "source": [
        "with keras.utils.custom_object_scope(custom_objects):\n",
        "    new_model = keras.models.clone_model(model)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wq1Dgi9eZUrR"
      },
      "source": [
        "## 모델의 가중치 값만 저장 및 로딩\n",
        "\n",
        "모델의 가중치 값만 저장하고 로드하도록 선택할 수 있습니다. 다음과 같은 경우에 유용할 수 있습니다.\n",
        "\n",
        "- 추론을 위한 모델만 필요합니다. 이 경우 훈련을 다시 시작할 필요가 없으므로 컴파일 정보나 옵티마이저 상태가 필요하지 않습니다.\n",
        "- 전이 학습을 수행하고 있습니다. 이 경우 이전 모델의 상태를 재사용하는 새 모델을 훈련하므로 이전 모델의 컴파일 정보가 필요하지 않습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dRJgbG8Zq7WB"
      },
      "source": [
        "### 인메모리 가중치 전이를 위한 API\n",
        "\n",
        "`get_weights` 및 `set_weights`를 사용하여 다른 객체 간에 가중치를 복사할 수 있습니다.\n",
        "\n",
        "- `tf.keras.layers.Layer.get_weights()`: numpy 배열의 리스트를 반환합니다.\n",
        "- `tf.keras.layers.Layer.set_weights()`: `weights` 인수 내 값으로 모델의 가중치를 설정합니다.\n",
        "\n",
        "다음은 예제입니다.\n",
        "\n",
        "***메모리에서 레이어 간 가중치 전이하기***"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "xXT0h7yxAA4e"
      },
      "outputs": [],
      "source": [
        "def create_layer():\n",
        "    layer = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")\n",
        "    layer.build((None, 784))\n",
        "    return layer\n",
        "\n",
        "\n",
        "layer_1 = create_layer()\n",
        "layer_2 = create_layer()\n",
        "\n",
        "# Copy weights from layer 2 to layer 1\n",
        "layer_2.set_weights(layer_1.get_weights())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IvCxdjmy6eKA"
      },
      "source": [
        "***메모리에서 호환 가능한 아키텍처를 사용하여 모델 간 가중치 전이하기***"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "CleccO1um5WU"
      },
      "outputs": [],
      "source": [
        "# Create a simple functional model\n",
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "outputs = keras.layers.Dense(10, name=\"predictions\")(x)\n",
        "functional_model = keras.Model(inputs=inputs, outputs=outputs, name=\"3_layer_mlp\")\n",
        "\n",
        "# Define a subclassed model with the same architecture\n",
        "class SubclassedModel(keras.Model):\n",
        "    def __init__(self, output_dim, name=None):\n",
        "        super(SubclassedModel, self).__init__(name=name)\n",
        "        self.output_dim = output_dim\n",
        "        self.dense_1 = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")\n",
        "        self.dense_2 = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")\n",
        "        self.dense_3 = keras.layers.Dense(output_dim, name=\"predictions\")\n",
        "\n",
        "    def call(self, inputs):\n",
        "        x = self.dense_1(inputs)\n",
        "        x = self.dense_2(x)\n",
        "        x = self.dense_3(x)\n",
        "        return x\n",
        "\n",
        "    def get_config(self):\n",
        "        return {\"output_dim\": self.output_dim, \"name\": self.name}\n",
        "\n",
        "\n",
        "subclassed_model = SubclassedModel(10)\n",
        "# Call the subclassed model once to create the weights.\n",
        "subclassed_model(tf.ones((1, 784)))\n",
        "\n",
        "# Copy weights from functional_model to subclassed_model.\n",
        "subclassed_model.set_weights(functional_model.get_weights())\n",
        "\n",
        "assert len(functional_model.weights) == len(subclassed_model.weights)\n",
        "for a, b in zip(functional_model.weights, subclassed_model.weights):\n",
        "    np.testing.assert_allclose(a.numpy(), b.numpy())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "V42tpJDicL4v"
      },
      "source": [
        "***상태 비저장 레이어의 경우***\n",
        "\n",
        "상태 비저장 레이어는 순서 또는 가중치 수를 변경하지 않기 때문에 상태 비저장 레이어가 남거나 없더라도 모델은 호환 가능한 아키텍처를 가질 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TWVjoCuVP6to"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "outputs = keras.layers.Dense(10, name=\"predictions\")(x)\n",
        "functional_model = keras.Model(inputs=inputs, outputs=outputs, name=\"3_layer_mlp\")\n",
        "\n",
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "\n",
        "# Add a dropout layer, which does not contain any weights.\n",
        "x = keras.layers.Dropout(0.5)(x)\n",
        "outputs = keras.layers.Dense(10, name=\"predictions\")(x)\n",
        "functional_model_with_dropout = keras.Model(\n",
        "    inputs=inputs, outputs=outputs, name=\"3_layer_mlp\"\n",
        ")\n",
        "\n",
        "functional_model_with_dropout.set_weights(functional_model.get_weights())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tUrgZcDAYaML"
      },
      "source": [
        "### 디스크에 가중치를 저장하고 다시 로딩하기 위한 API\n",
        "\n",
        "다음 형식으로 `model.save_weights`를 호출하여 디스크에 가중치를 저장할 수 있습니다.\n",
        "\n",
        "- TensorFlow Checkpoint\n",
        "- HDF5\n",
        "\n",
        "`model.save_weights`의 기본 형식은 TensorFlow 체크포인트입니다. 저장 형식을 지정하는 두 가지 방법이 있습니다.\n",
        "\n",
        "1. `save_format` 인수: `save_format=\"tf\"` 또는 `save_format=\"h5\"`에 값을 설정합니다.\n",
        "2. `path` 인수: 경로가 `.h5` 또는 `.hdf5`로 끝나면 HDF5 형식이 사용됩니다. `save_format`을 설정하지 않으면 다른 접미어의 경우 TensorFlow 체크포인트로 결과가 발생합니다.\n",
        "\n",
        "인메모리 numpy 배열로 가중치를 검색하는 옵션도 있습니다. 각 API에는 장단점이 있으며 아래에서 자세히 설명합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "de8G1QVux2za"
      },
      "source": [
        "### TF Checkpoint 형식\n",
        "\n",
        "**예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1W82BZuskILz"
      },
      "outputs": [],
      "source": [
        "# Runnable example\n",
        "sequential_model = keras.Sequential(\n",
        "    [\n",
        "        keras.Input(shape=(784,), name=\"digits\"),\n",
        "        keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\"),\n",
        "        keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\"),\n",
        "        keras.layers.Dense(10, name=\"predictions\"),\n",
        "    ]\n",
        ")\n",
        "sequential_model.save_weights(\"ckpt\")\n",
        "load_status = sequential_model.load_weights(\"ckpt\")\n",
        "\n",
        "# `assert_consumed` can be used as validation that all variable values have been\n",
        "# restored from the checkpoint. See `tf.train.Checkpoint.restore` for other\n",
        "# methods in the Status object.\n",
        "load_status.assert_consumed()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CUDB1dkiecxZ"
      },
      "source": [
        "#### 형식 세부 사항\n",
        "\n",
        "TensorFlow Checkpoint 형식은 객체 속성명을 사용하여 가중치를 저장하고 복원합니다. 예를 들어, `tf.keras.layers.Dense` 레이어를 고려해 봅시다. 레이어에는 `dense.kernel`과 `dense.bias` 두 가지 가중치가 있습니다. 레이어가 `tf` 형식으로 저장되면 결과 체크포인트에는 `\"kernel\"` 및 `\"bias\"`와 해당 가중치 값이 포함됩니다. 자세한 정보는 [TF Checkpoint 가이드의 \"로딩 메커니즘\"](https://www.tensorflow.org/guide/checkpoint#loading_mechanics)을 참조하세요.\n",
        "\n",
        "속성/그래프 에지는 **변수명이 아니라 부모 객체에서 사용된 이름**에 따라 이름이 지정됩니다. 아래 예제의 `CustomLayer`를 고려해 봅시다. 변수 `CustomLayer.var`는 `\"var_a\"`가 아니라, 키의 일부로서 `\"var\"`로 저장됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wwjjEg7zQ29O"
      },
      "outputs": [],
      "source": [
        "class CustomLayer(keras.layers.Layer):\n",
        "    def __init__(self, a):\n",
        "        self.var = tf.Variable(a, name=\"var_a\")\n",
        "\n",
        "\n",
        "layer = CustomLayer(5)\n",
        "layer_ckpt = tf.train.Checkpoint(layer=layer).save(\"custom_layer\")\n",
        "\n",
        "ckpt_reader = tf.train.load_checkpoint(layer_ckpt)\n",
        "\n",
        "ckpt_reader.get_variable_to_dtype_map()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tfdbha2TvYWH"
      },
      "source": [
        "#### 전이 학습 예제\n",
        "\n",
        "기본적으로 두 모델이 동일한 아키텍처를 갖는 한 동일한 검사점을 공유할 수 있습니다.\n",
        "\n",
        "**예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6Xqhxo35q0qj"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "outputs = keras.layers.Dense(10, name=\"predictions\")(x)\n",
        "functional_model = keras.Model(inputs=inputs, outputs=outputs, name=\"3_layer_mlp\")\n",
        "\n",
        "# Extract a portion of the functional model defined in the Setup section.\n",
        "# The following lines produce a new model that excludes the final output\n",
        "# layer of the functional model.\n",
        "pretrained = keras.Model(\n",
        "    functional_model.inputs, functional_model.layers[-1].input, name=\"pretrained_model\"\n",
        ")\n",
        "# Randomly assign \"trained\" weights.\n",
        "for w in pretrained.weights:\n",
        "    w.assign(tf.random.normal(w.shape))\n",
        "pretrained.save_weights(\"pretrained_ckpt\")\n",
        "pretrained.summary()\n",
        "\n",
        "# Assume this is a separate program where only 'pretrained_ckpt' exists.\n",
        "# Create a new functional model with a different output dimension.\n",
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "outputs = keras.layers.Dense(5, name=\"predictions\")(x)\n",
        "model = keras.Model(inputs=inputs, outputs=outputs, name=\"new_model\")\n",
        "\n",
        "# Load the weights from pretrained_ckpt into model.\n",
        "model.load_weights(\"pretrained_ckpt\")\n",
        "\n",
        "# Check that all of the pretrained weights have been loaded.\n",
        "for a, b in zip(pretrained.weights, model.weights):\n",
        "    np.testing.assert_allclose(a.numpy(), b.numpy())\n",
        "\n",
        "print(\"\\n\", \"-\" * 50)\n",
        "model.summary()\n",
        "\n",
        "# Example 2: Sequential model\n",
        "# Recreate the pretrained model, and load the saved weights.\n",
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "pretrained_model = keras.Model(inputs=inputs, outputs=x, name=\"pretrained\")\n",
        "\n",
        "# Sequential example:\n",
        "model = keras.Sequential([pretrained_model, keras.layers.Dense(5, name=\"predictions\")])\n",
        "model.summary()\n",
        "\n",
        "pretrained_model.load_weights(\"pretrained_ckpt\")\n",
        "\n",
        "# Warning! Calling `model.load_weights('pretrained_ckpt')` won't throw an error,\n",
        "# but will *not* work as expected. If you inspect the weights, you'll see that\n",
        "# none of the weights will have loaded. `pretrained_model.load_weights()` is the\n",
        "# correct method to call."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eCsRvSzqMJ0s"
      },
      "source": [
        "일반적으로 모델을 빌드할 때 동일한 API를 사용하는 것이 좋습니다. Sequential 및 Functional 또는 Functional 및 서브 클래스 등 간에 전환하는 경우, 항상 사전 훈련된 모델을 다시 빌드하고 사전 훈련된 가중치를 해당 모델에 로드합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "a9EmwUaZBTeW"
      },
      "source": [
        "다음 질문은 모델 아키텍처가 상당히 다른 경우 어떻게 다른 모델에 가중치를 저장하고 로드하는가입니다. 해결책은 `tf.train.Checkpoint`를 사용하여 정확한 레이어/변수를 저장하고 복원하는 것입니다.\n",
        "\n",
        "**예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "j6jE9sz7yQ9b"
      },
      "outputs": [],
      "source": [
        "# Create a subclassed model that essentially uses functional_model's first\n",
        "# and last layers.\n",
        "# First, save the weights of functional_model's first and last dense layers.\n",
        "first_dense = functional_model.layers[1]\n",
        "last_dense = functional_model.layers[-1]\n",
        "ckpt_path = tf.train.Checkpoint(\n",
        "    dense=first_dense, kernel=last_dense.kernel, bias=last_dense.bias\n",
        ").save(\"ckpt\")\n",
        "\n",
        "# Define the subclassed model.\n",
        "class ContrivedModel(keras.Model):\n",
        "    def __init__(self):\n",
        "        super(ContrivedModel, self).__init__()\n",
        "        self.first_dense = keras.layers.Dense(64)\n",
        "        self.kernel = self.add_variable(\"kernel\", shape=(64, 10))\n",
        "        self.bias = self.add_variable(\"bias\", shape=(10,))\n",
        "\n",
        "    def call(self, inputs):\n",
        "        x = self.first_dense(inputs)\n",
        "        return tf.matmul(x, self.kernel) + self.bias\n",
        "\n",
        "\n",
        "model = ContrivedModel()\n",
        "# Call model on inputs to create the variables of the dense layer.\n",
        "_ = model(tf.ones((1, 784)))\n",
        "\n",
        "# Create a Checkpoint with the same structure as before, and load the weights.\n",
        "tf.train.Checkpoint(\n",
        "    dense=model.first_dense, kernel=model.kernel, bias=model.bias\n",
        ").restore(ckpt_path).assert_consumed()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1R9zCAelVexH"
      },
      "source": [
        "### HDF5 format\n",
        "\n",
        "HDF5 형식에는 레이어 이름별로 그룹화된 가중치가 포함됩니다. 가중치는 훈련 가능한 가중치 목록을 훈련 불가능한 가중치 목록(`layer.weights`와 동일)에 연결하여 정렬된 목록입니다. 따라서 모델이 체크포인트에 저장된 것과 동일한 레이어 및 훈련 가능한 상태를 갖는 경우 hdf5 체크포인트을 사용할 수 있습니다.\n",
        "\n",
        "**예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "J2LictZSclDh"
      },
      "outputs": [],
      "source": [
        "# Runnable example\n",
        "sequential_model = keras.Sequential(\n",
        "    [\n",
        "        keras.Input(shape=(784,), name=\"digits\"),\n",
        "        keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\"),\n",
        "        keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\"),\n",
        "        keras.layers.Dense(10, name=\"predictions\"),\n",
        "    ]\n",
        ")\n",
        "sequential_model.save_weights(\"weights.h5\")\n",
        "sequential_model.load_weights(\"weights.h5\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rCy09yfqXQT8"
      },
      "source": [
        "모델에 중첩된 레이어가 포함된 경우 `layer.trainable`을 변경하면 `layer.weights`의 순서가 다르게 나타날 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "VX8hFyI9HgYT"
      },
      "outputs": [],
      "source": [
        "class NestedDenseLayer(keras.layers.Layer):\n",
        "    def __init__(self, units, name=None):\n",
        "        super(NestedDenseLayer, self).__init__(name=name)\n",
        "        self.dense_1 = keras.layers.Dense(units, name=\"dense_1\")\n",
        "        self.dense_2 = keras.layers.Dense(units, name=\"dense_2\")\n",
        "\n",
        "    def call(self, inputs):\n",
        "        return self.dense_2(self.dense_1(inputs))\n",
        "\n",
        "\n",
        "nested_model = keras.Sequential([keras.Input((784,)), NestedDenseLayer(10, \"nested\")])\n",
        "variable_names = [v.name for v in nested_model.weights]\n",
        "print(\"variables: {}\".format(variable_names))\n",
        "\n",
        "print(\"\\nChanging trainable status of one of the nested layers...\")\n",
        "nested_model.get_layer(\"nested\").dense_1.trainable = False\n",
        "\n",
        "variable_names_2 = [v.name for v in nested_model.weights]\n",
        "print(\"\\nvariables: {}\".format(variable_names_2))\n",
        "print(\"variable ordering changed:\", variable_names != variable_names_2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "V4GHHReOFEGq"
      },
      "source": [
        "#### 전이 학습 예제\n",
        "\n",
        "HDF5에서 사전 훈련된 가중치를 로딩할 때는 가중치를 기존 체크포인트 모델에 로드한 다음 원하는 가중치/레이어를 새 모델로 추출하는 것이 좋습니다.\n",
        "\n",
        "**예제:**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YcgjA7yYG49d"
      },
      "outputs": [],
      "source": [
        "def create_functional_model():\n",
        "    inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "    x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "    x = keras.layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "    outputs = keras.layers.Dense(10, name=\"predictions\")(x)\n",
        "    return keras.Model(inputs=inputs, outputs=outputs, name=\"3_layer_mlp\")\n",
        "\n",
        "\n",
        "functional_model = create_functional_model()\n",
        "functional_model.save_weights(\"pretrained_weights.h5\")\n",
        "\n",
        "# In a separate program:\n",
        "pretrained_model = create_functional_model()\n",
        "pretrained_model.load_weights(\"pretrained_weights.h5\")\n",
        "\n",
        "# Create a new model by extracting layers from the original model:\n",
        "extracted_layers = pretrained_model.layers[:-1]\n",
        "extracted_layers.append(keras.layers.Dense(5, name=\"dense_3\"))\n",
        "model = keras.Sequential(extracted_layers)\n",
        "model.summary()"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "save_and_serialize.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
